package cn.jason.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * @Author: Jason
 * @Date: 2022/9/19 18:53
 */
public class ZookeeperDistributedLock implements ItsourceLock {

    ZkClient client = new ZkClient("127.0.0.1:2181", 5000);
    CountDownLatch cdl = new CountDownLatch(1); //不为零阻塞住，不让他往下走

    //父节点路径
    String parent = "";
    //当前节点路径
    String currentPath = "";

    //加锁逻辑
    @Override
    public void lock(String resourceName) {
        //resourceName：goods_1
        parent = "/"+resourceName;
        //parent：/goods_1
        //判断父节点是否存在，如果不存在要创建一个持久节点
        if (!client.exists(parent)){
            //注意：这里第一次运行测试时，会报错，是正常情况，因为这里第一次有10个线程进来，都会进入到if里面，那就有1个创建节点成功，其余9个全部创建失败，再运行一次就可以了
            client.createPersistent(parent,"root");
        }
        //前面的节点都处理完成，自己变成第一个节点才加锁成功。
        if (!tryLock(resourceName)){
            //自旋
            lock(resourceName);
        }
        //获取到锁了走这里
    }

    //尝试获取锁
    @Override
    public boolean tryLock(String resourceName) {
        //resourceName：goods_1
        //创建子节点-临时顺序节点
        if (StringUtils.isEmpty(currentPath)){
            //创建临时顺序节点
            //parent：/goods_1
            currentPath = client.createEphemeralSequential(parent + "/test", "test");
            //currentPath值：/goods_1/test000000001   T1
            //currentPath值：/goods_1/test000000002   T2
            //currentPath值：/goods_1/test000000003   T3
        }
        //如果是第一个节点，就获取到锁了。
        //parent：/goods_1
        List<String> children = client.getChildren(parent);
        /**
         * children的值：
         * /test000000002
         * /test000000003
         * /test000000001
         */
        //排序
        Collections.sort(children);
        /**
         * 排序之后的children的值：
         * /test000000001   T1
         * /test000000002   T2
         * /test000000003   T3
         */
        //currentPath：/goods_1/test000000001
        //children.get(0) ： /goods_1/test000000001
        if (currentPath.contains(children.get(0))){
            return true;
        }else{
            ///goods_1/test000000002
            //如果不是第一个节点，监听前一个节点，要再这儿等待，知道被触发，再次判断是否是第一个节点进行返回就OK
            String str = currentPath.substring(currentPath.lastIndexOf("/")+1);//test000000002  T2
            int preIndex = children.indexOf(str)-1;//0(T2)    1(T3)
            //T2:prePath：/goods_1/test000000001
            //T3:prePath：/goods_1/test000000002
            String prePath = parent+"/"+children.get(preIndex);
            //监听上一个节点，如果上一个节点被删除，把秒表设置为 0 （cdl.countDown();），那么当前节点取消等待(cdl.await();)重新获取锁
            client.subscribeDataChanges(prePath, new IZkDataListener() {
                @Override
                public void handleDataChange(String s, Object o) throws Exception {
                    //具体业务实现代码
                }
                @Override
                public void handleDataDeleted(String dataPath) throws Exception {
                    //让他-1 变为零
                    cdl.countDown();
                }
            });
            //秒表等待，一直等待，直到自己变成第一个节点
            try {
                cdl.await();//T2
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return false;
        }
    }

    //释放锁
    @Override
    public void unlock(String resourceName) {
        //自己操作完毕，删除自己，让下一个节点执行。
        client.delete(currentPath);//T1线程：/goods_1/test000000001
        client.close();//关闭会话
    }
}
